import re
import random
import string
import shutil
import os

from pathlib import Path

# 配置项
OBFUSCATE_LENGTH = 16  # 混淆名长度
BACKUP_SUFFIX = ".original"  # 原始文件备份后缀
EXCLUDE_NAMES = {
    # 文件名
    "swift",
    # 系统保留字
    "init", "deinit", "self", "super", "get", "set", "didSet", "willSet",
    "func", "var", "let", "class", "struct", "enum", "protocol",
    # 系统控件常用属性（UIKit/AppKit核心属性）
    "frame", "bounds", "center", "origin", "size", "width", "height",
    "backgroundColor", "tintColor", "alpha", "isHidden", "isEnabled",
    "text", "image", "title", "contentSize", "count", "index", "subviews",
    "superview", "layer", "navigationItem", "tabBarItem", "dataSource",
    "delegate", "row", "section", "cell", "view", "window", "controller","titleLabel",
    "completion","debugPrint","readPackets","setTunnelNetwork",
}
EXCLUDE_PREFIXES = (  # 不混淆系统框架相关的名称（如UI、NS前缀）
    "UI", "NS", "CF", "CG", "AV", "GL", "SK", "WK","UU","_","Qianlikit","Qianli_"
)
# 常见系统协议方法的特征（方法名前缀）
PROTOCOL_METHOD_PREFIXES = (
    "textView", "tableView", "collectionView", "scrollView", 
    "button", "view", "controller", "cell", "pickerView","animationController",
    "products","payment","observe","webView","awakeFromNib","layout","setNeedsLayout",
    "setSelected","animationDid","touches","stopTunnel","cancelTunnel","handleAppMessage",
    
)

# 生成随机混淆名（字母开头，避免纯数字）
def generate_obfuscated_name():
    # 首字符用字母，避免数字开头导致语法错误
    first_char = random.choice(string.ascii_letters)
    rest_chars = ''.join(random.choices(string.ascii_letters + string.digits, k=OBFUSCATE_LENGTH-1))
    return first_char + rest_chars

# 映射表管理（支持持久化，避免重复混淆）
class NameMapping:
    def __init__(self, mapping_file="name_mapping.txt"):
        #name_mapping.txt,没指定目录，于是会默认在 是执行脚本时终端 / 命令行所处的目录
        #命令执行是在：zifandeMacBook-Pro:python_demo zifan$ 
        # 故name_mapping.txt生成在了zifandeMacBook-Pro:python_demo目录下
        self.mapping_file = mapping_file
        self.original_to_obfuscated = {}
        self.obfuscated_to_original = {}
        
        self._load_existing()

    def _load_existing(self):
        """加载已有的映射表，避免重复生成"""
        if Path(self.mapping_file).exists():
            with open(self.mapping_file, 'r', encoding='utf-8') as f:
                for line in f:
                    line = line.strip()
                    if '→' in line:
                        original, obfuscated = line.split('→', 1)
                        original = original.strip()
                        obfuscated = obfuscated.strip()
                        self.original_to_obfuscated[original] = obfuscated
                        self.obfuscated_to_original[obfuscated] = original

    def get_obfuscated(self, original):
        """获取混淆名，已存在则复用，否则生成新的"""
        if original in self.original_to_obfuscated:
            return self.original_to_obfuscated[original]
        elif original in self.obfuscated_to_original: #对已混淆的文件二次混淆，本身已经是混淆过的值，且有对应的值
            print(f"----get_obfuscated>已是混淆:{original}")
            return original
        
        # 检查是否需要排除
        if original in EXCLUDE_NAMES or original.startswith(EXCLUDE_PREFIXES):
            print(f"----get_obfuscated>排除:{original}")
            return original
        
        # 生成不重复的混淆名
        while True:
            obfuscated = generate_obfuscated_name()
                
            if obfuscated not in self.obfuscated_to_original:
                self.original_to_obfuscated[original] = obfuscated
                self.obfuscated_to_original[obfuscated] = original
                return obfuscated

    def save(self):
        """保存映射表"""
        with open(self.mapping_file, 'w', encoding='utf-8') as f:
            for original, obfuscated in self.original_to_obfuscated.items():
                f.write(f"{original} → {obfuscated}\n")

# 代码混淆核心类
class SwiftObfuscator:
    def load_existing_hunxiao(self):
        if Path(self.mapping.mapping_file).exists():
            with open(self.mapping.mapping_file, 'r', encoding='utf-8') as f:
                for line in f:
                    line = line.strip()
                    if '→' in line:
                        original, _ = line.split('→', 1)
                        original = original.strip()
                        # self.custom_types.add(original)
                        self.obfuscated_methods.add(original)
                        self.obfuscated_privateProperties.add(original)
                        
    def __init__(self, mapping: NameMapping):
        self.mapping = mapping
        # 共享的方法映射表（跨文件复用）
        self.custom_types = set()  # 存储所有自定义类型名称（类/结构体/枚举）
        self.obfuscated_methods = set()   # 仅记录被混淆的方法名（原始名）
        self.obfuscated_privateProperties = set()   # 仅记录被混淆的私有属性名（原始名）
        self.load_existing_hunxiao()
        # ------------
        
        
        # 预编译正则（处理不同语法结构）
        self.patterns = {
            # 1. 匹配自定义类型定义（用于收集custom_types）
            "custom_type": re.compile(r'(class|struct|enum)\s+(\w+)\s*[:{]'),
            # 2. 匹配自定义类型内部的属性（只在自定义类型作用域内混淆）
            # "custom_property": re.compile(r'(var|let)\s+(\w+)\s*[:=]'),
            # 只对每个文件内的私有属性混淆
            "custom_property" : re.compile(r'(private\s+)(var|let)\s+(\w+)\s*[:=]'),
            # 3. 匹配自定义类型内部的方法（只在自定义类型作用域内混淆）
            "custom_method": re.compile(r'func\s+(\w+)\s*\('),
            # 4. 匹配自定义类型实例的方法调用（如 myObj.method()）
            "custom_method_call": re.compile(r'(?:(\w+)\s*\.)?(\w+)\s*\('),
            # 新增：selector中的方法名正则
            "selector_method": re.compile(r'#selector\(\s*(\w+)\s*\)',re.UNICODE), 
            # 5. 匹配自定义类型实例的属性访问（如 myObj.property）
            # "UI", "NS", "CF", "CG", "AV", "GL", "SK", "WK","UU","Qianlikit"
            "custom_property_call": re.compile(r'\b(?!(UI|NS|CF|CG|AV|GL|SK|WK|UU|Qianlikit)\.)([a-zA-Z_]\w*)\s*\.\s*([a-zA-Z_]\w*)\b',re.UNICODE),
            "custom_3_call":re.compile(r'self(\.|(\?|!)\.)([a-zA-Z_]\w*)(?:\.([a-zA-Z_]\w*))?',re.UNICODE)
        }
        
    def _obfuscate_custom_types(self, content):
        """第一步：提取所有自定义类型名称（类/结构体/枚举）"""
        # 1.遍历所有需要混淆的自定义类型
        matches = self.patterns["custom_type"].finditer(content)
        for match in matches:
            type_name = match.group(2)
            # 排除系统类型（通过前缀判断）
            if not type_name.startswith(EXCLUDE_PREFIXES):
                self.custom_types.add(type_name)   #------ 全局记录混淆的类名
                print(f"识别到自定义类型：{type_name}")
        print(f"共识别{len(self.custom_types)}个自定义类型")
        
        # 2：混淆类定义中的类名，并替换所有使用该类名的地方
        if self.custom_types:
            for original_class in list(self.custom_types):
                # 生成混淆名
                obfuscated_class = self.mapping.get_obfuscated(original_class)
                if original_class == obfuscated_class:
                    continue  # 无需混淆
                # a.替换类定义中的类名（如 "class Original: Parent" → "class Obfuscated: Parent"）
                class_def_pattern = re.compile(
                    rf'(class|struct|enum)\s+{original_class}\s*:',
                    re.IGNORECASE
                )
                content = class_def_pattern.sub(
                    lambda m: f'{m.group(1)} {obfuscated_class}:',
                    content
                )
                print(f"混淆类定义：{original_class} → {obfuscated_class}")
                
                # b. 替换代码中所有使用该类名的地方（如实例化、类型引用）
                class_usage_pattern = re.compile(
                    rf'\b{original_class}\b(?!:)',  # 排除父类声明中的类名（如": Original"）
                    re.IGNORECASE
                )
                content = class_usage_pattern.sub(
                    obfuscated_class,
                    content
                )
                print(f"替换类使用：{original_class} → {obfuscated_class}")
        return content
            
        
    def _obfuscate_custom_members(self, content):
        """第二步：只混淆自定义类的私属性和方法"""
        # 1. 处理自定义类型内部的属性
        property_matches = list(self.patterns["custom_property"].finditer(content))
        for match in reversed(property_matches):
            # 判断属性是否在自定义类型的作用域内（简化版：通过上下文包含"class XXX"判断）
            name = match.group(3)
            obfuscated = self.mapping.get_obfuscated(name)
            if name != obfuscated:
                self.obfuscated_privateProperties.add(name) #------全局记录被混淆的属性名（原始名）
                
                start, end = match.span(3)
                content = content[:start] + obfuscated + content[end:]
                print(f"混淆自定义属性：{name} → {obfuscated}")
                    
        # 2. 处理自定义类型内部的方法
        method_matches = list(self.patterns["custom_method"].finditer(content))
        for match in reversed(method_matches):
            name = match.group(1)
            # 判断排除该方法是不是协议方法，
            if self._is_protocol_method(name):
                print(f"跳过协议方法：{name}")
                continue
            
            obfuscated = self.mapping.get_obfuscated(name)
            if name != obfuscated:
                self.obfuscated_methods.add(name)  #------- 全局记录被混淆的方法名（原始名）
                
                start, end = match.span(1)
                content = content[:start] + obfuscated + content[end:]
                print(f"混淆自定义方法：{name} → {obfuscated}")

        return content

    def _obfuscate_custom_method_calls(self, content):
        """第三步：只混淆自定义类型实例的调用（如 myObj.method()）"""
        # 处理自定义实例的方法调用（如 myObj.method()）
        methods_to_replace = self.obfuscated_methods
        if not methods_to_replace:
            return content #没有被标记为已混淆的方法或属性，说明不需要混淆，故在处理调用的时候也不混淆
        
        call_matches = list(self.patterns["selector_method"].finditer(content))
        for match in reversed(call_matches):
            method_name = match.group(1)    # 方法名（如 method）
            # 关键判断：只替换已被混淆的方法名
            if (method_name in methods_to_replace):
                obfuscated = self.mapping.get_obfuscated(method_name)
                method_start, method_end = match.span(1)
                content = content[:method_start] + obfuscated + content[method_end:]
                print(f"混淆方法调用：#selector.{method_name} → {obfuscated}")

        call_matches = list(self.patterns["custom_method_call"].finditer(content))
        for match in reversed(call_matches):
            instance_name = match.group(1)  # 实例名（如 myObj，self,也可能为none，直接调用当前方法，不需要实例的情况）
            method_name = match.group(2)    # 方法名（如 method）
            # 关键判断：只替换已被混淆的方法名
            if (method_name in methods_to_replace):
                obfuscated = self.mapping.get_obfuscated(method_name)
                method_start, method_end = match.span(2)
                content = content[:method_start] + obfuscated + content[method_end:]
                print(f"混淆方法调用：{instance_name}.{method_name} → {obfuscated}")
        
        return content
    
    def _obfuscate_custom_property_calls(self, content):
        # 处理自定义实例的属性访问（如 myObj.property）
        property_to_replace = self.obfuscated_privateProperties
        if not property_to_replace:
            return content
        
        prop_call_matches = list(self.patterns["custom_property_call"].finditer(content))
        for match in reversed(prop_call_matches):
            instance_name = match.group(2)
            prop_name = match.group(3)
            # 关键判断：只替换已被混淆的方法名
            if instance_name and (prop_name in property_to_replace):
                obfuscated = self.mapping.get_obfuscated(prop_name)
                method_start, method_end = match.span(3)
                content = content[:method_start] + obfuscated + content[method_end:]
                print(f"混淆自定义属性访问：{instance_name}.{prop_name} → {obfuscated}")
                
        prop_call_matches = list(self.patterns["custom_3_call"].finditer(content))
        methods_to_replace = self.obfuscated_methods
        for match in reversed(prop_call_matches):
            symbol = match.group(1)  # 对象
            self_type = 'self'  # self.xxx 形式
            if symbol == '?.':
                self_type = 'self?' # self?.xxx 形式
            elif symbol == '!.':
                self_type = 'self!' # self!.xxx 形式
            level1 = match.group(3)  # 第一级成员（2级调用中是方法名，3级调用中是属性名）
            level2 = match.group(4)  # 第二级成员（可选，3级调用中是方法名）
            iSpan = 0
            if level2:
                instance_name = f"{self_type}.{level1}"
                prop_name = level2
                iSpan = 4
            elif level1:
                instance_name = self_type
                prop_name = level1
                iSpan = 3
            else:
                print(f"err:{match}")
                continue
            # 关键判断：只替换已被混淆的方法名
            if methods_to_replace and instance_name and (prop_name in methods_to_replace):
                obfuscated = self.mapping.get_obfuscated(prop_name)
                method_start, method_end = match.span(iSpan)
                content = content[:method_start] + obfuscated + content[method_end:]
                print(f"补充-混淆自定义方法访问：{instance_name}.{prop_name} → {obfuscated}")
            elif property_to_replace and instance_name and (prop_name in property_to_replace):
                obfuscated = self.mapping.get_obfuscated(prop_name)
                method_start, method_end = match.span(iSpan)
                content = content[:method_start] + obfuscated + content[method_end:]
                print(f"补充-混淆自定义属性访问：{instance_name}.{prop_name} → {obfuscated}")

        return content
    
    def obfuscate_content(self, content):
        """混淆代码内容"""
        result = content
        # 步骤1：先识别所有自定义类
        # result = self._obfuscate_custom_types(content)
        
        # 步骤2：混淆自定义类型内部的属性和方法
        result = self._obfuscate_custom_members(result)
        
        # 步骤3：混淆自定义实例的方法调用
        result = self._obfuscate_custom_method_calls(result)
        # 步骤4：混淆自定义实例的属性调用
        result = self._obfuscate_custom_property_calls(result)
        return result
    
    def obfuscate_file(self, file_path):
        file_path = Path(file_path)
        if not file_path.exists() or file_path.suffix != ".swift":
            print(f"跳过无效文件：{file_path}")
            return

        backup_path = file_path.with_suffix(f"{file_path.suffix}{BACKUP_SUFFIX}")
        if not backup_path.exists():
            shutil.copy2(file_path, backup_path)

        with open(file_path, 'r', encoding='utf-8') as f:
            content = f.read()

        obfuscated_content = self.obfuscate_content(content)

        with open(file_path, 'w', encoding='utf-8') as f:
            f.write(obfuscated_content)
        print(f"已混淆文件：{file_path}")
        
    def _is_protocol_method(self, method_name):
        """判断是否为系统协议方法"""
        # 特征1：方法名包含协议前缀（如textView、tableView）
        if any(method_name.startswith(prefix) for prefix in PROTOCOL_METHOD_PREFIXES):
            return True
        return False

# 执行入口
if __name__ == '__main__':
    # 初始化映射表
    name_mapping = NameMapping()
    obfuscator = SwiftObfuscator(name_mapping)
    
    # 需要混淆的文件列表（可替换为实际路径）
    script_dir = os.path.dirname(os.path.abspath(__file__))# 构建目标文件相对于脚本所在目录的路径
    
    target_files = [
        # os.path.join(script_dir,'todoios/M/QianliUniversalFunc.swift'),
        # os.path.join(script_dir,'todoios/M/QianliVibrateEnum.swift'),
        # os.path.join(script_dir,'todoios/M/QianliAppEnvStruct.swift'),
        # os.path.join(script_dir,'todoios/M/QianliLuanchProc.swift'),
        
        # os.path.join(script_dir,'todoios/V/ArticleInfoCell.swift'),
        # os.path.join(script_dir,'todoios/V/GradientBtn.swift'),
        # os.path.join(script_dir,'todoios/V/InputMailView.swift'),
        # os.path.join(script_dir,'todoios/V/InputPasswordView.swift'),
        
        # os.path.join(script_dir,'todoios/V/InviteDurationCell.swift'),
        # os.path.join(script_dir,'todoios/V/MineView.swift'),
        
        # os.path.join(script_dir,'todoios/V/NodeItemCell.swift'),
        # os.path.join(script_dir,'todoios/V/OrderItemCell.swift'),
        
        # os.path.join(script_dir,'todoios/V/ProfileButton.swift'),
        
        # os.path.join(script_dir,'todoios/V/RetryNet.swift'),
        # os.path.join(script_dir,'todoios/V/VipMemberItem.swift'),
        
        # os.path.join(script_dir,'todoios/V/QianliAppearProgressIndicator.swift'),
        # os.path.join(script_dir,'todoios/V/QianliAppearProgressView.swift'),
        
        # os.path.join(script_dir,'todoios/V/QianliAppearView.swift'),
        
        # os.path.join(script_dir,'todoios/ClashManager/QianliVPNController.swift'),
        # os.path.join(script_dir,'todoios/ClashManager/QianliVpnManager.swift'),
        
        # os.path.join(script_dir,'todoios/handle/ZacollectModel.swift'),
        # os.path.join(script_dir,'todoios/handle/LoginViewModel.swift'),
        # os.path.join(script_dir,'todoios/handle/UserInfoViewModel.swift'),
        # os.path.join(script_dir,'todoios/handle/VipViewModel.swift'),
        
        # os.path.join(script_dir,'todoios/C/QianliParentNaviVC.swift'),
        # os.path.join(script_dir,'todoios/C/QainliProtocolWebPage.swift'),
        
        # os.path.join(script_dir,'todoios/C/QainliUserDetail+Device.swift'),
        # os.path.join(script_dir,'todoios/C/QainliUserLoginVC+Login.swift'),
        # os.path.join(script_dir,'todoios/C/QianliArticleVC+Notice.swift'),
        # os.path.join(script_dir,'todoios/C/QianliCountryVC+Health.swift'),
        # os.path.join(script_dir,'todoios/C/QianliMemberVC+VIP.swift'),
        # os.path.join(script_dir,'todoios/C/QianliUserLogoutVC+Reset.swift'),
        # os.path.join(script_dir,'todoios/C/QianliUserRegisterVC+Register.swift'),
        # os.path.join(script_dir,'todoios/C/QianMainWindowVC+Noti.swift'),
        
        # os.path.join(script_dir,'todoios/C/QianliResetUserpwVC.swift'),
        # os.path.join(script_dir,'todoios/C/QianliUserLogoutVC.swift'),
        # os.path.join(script_dir,'todoios/C/QainliUserLoginVC.swift'),
        
        # os.path.join(script_dir,'todoios/C/QianliArticleVC.swift'),
        # os.path.join(script_dir,'todoios/C/QianliInviteDetail.swift'),
        # os.path.join(script_dir,'todoios/C/QianliNotforgetVC.swift'),
        # os.path.join(script_dir,'todoios/C/QianliRefgxfcCqqnbVC.swift'),
        
        # os.path.join(script_dir,'todoios/C/QianliUserOrderlistVC.swift'),
        
        # os.path.join(script_dir,'todoios/C/QainliUserDetail.swift'),
        # os.path.join(script_dir,'todoios/C/QianliCountryVC.swift'),
        # os.path.join(script_dir,'todoios/C/QianliMemberVC.swift'),
        # os.path.join(script_dir,'todoios/C/QianliUserRegisterVC.swift'),
        os.path.join(script_dir,'todoios/C/QianMainWindowVC.swift'),

    ]
    
    # 执行混淆
    for file in target_files:
        obfuscator.obfuscate_file(file)
    
    # 保存映射表
    name_mapping.save()
    print(f"混淆完成，映射表已保存到: {name_mapping.mapping_file}")

#注意swift代码中：
# 方法名避免与系统方法名有冲突 
# 私有属性及方法，都加上private
# _开头的属性不混淆,Qianli_开头的方法不混然(一般为public方法避免混淆后交叉调用有误)